package com.yanqu.road.server.logic.smallgame.stage;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.yanqu.road.entity.config.smallgame.SmallGameConfig;
import com.yanqu.road.entity.config.smallgame.SmallGameType;
import com.yanqu.road.entity.enums.eLogMoneyType;
import com.yanqu.road.pb.smallgame.SmallGameProto;
import com.yanqu.road.server.logic.smallgame.SmallGame;
import com.yanqu.road.server.protocol.GameErrorCode;
import com.yanqu.road.utils.string.StringUtils;

import java.util.*;

@SmallGame(type = SmallGameType.GridSwitch, desc = "硕果累累")
public class GridSwitchStage implements SmallGameStageInterface {
    private Map<Integer, List<Integer>> gridMap;
    private int nowGridKey = 0;
    private int startKey = 0;
    private List<Integer> movedRoad;
    private Map<String, Integer> dfsVisit;
    private int moveTry = 0;
    private List<List<Integer>> answerList;

    private int length = 0;
    private int wide = 0;
    private int blankNum = 0;
    private int directionNum = 0;
    private boolean haveEnd;

    private Random random = new Random();

    public GridSwitchStage() {
        this.movedRoad = new ArrayList<>();
        this.answerList = new ArrayList<>();
        this.gridMap = new HashMap<>();
        this.dfsVisit = new HashMap<>();
    }

    public GridSwitchStage(int length, int wide, int startKey, int blankNum, int directionNum, boolean haveEnd) {
        //随机生成关卡
        /*
        初始固定长宽的格子都是无限制格子,扣掉空白格,获得一个列表,
        从起点开始用dfs配合随机遍历顺序,把这个列表按顺序压到格子里，
        然后把没遍历到的格子设置成空白格, 再随机directionNum个格子,并设置方向限制格子的朝向
        startKey-1表示用随机生成的，非-1用指定的位置
        * */
        this.length = length;
        this.wide = wide;
        this.blankNum = blankNum;
        this.directionNum = directionNum;
        this.haveEnd = haveEnd;
        this.startKey = startKey;
        this.movedRoad = new ArrayList<>();
        this.answerList = new ArrayList<>();
        this.gridMap = new HashMap<>();
        this.dfsVisit = new HashMap<>();
    }

    private Integer getKey(int x, int y) {
        return x * 10000 + y;
    }

    private Integer getGridType(int key) {
        if (!gridMap.containsKey(key)) return eGridSwitchType.Blank.getValue();
        if (gridMap.get(key).size() < 3) return eGridSwitchType.Blank.getValue();
        return gridMap.get(key).get(2);
    }

    private boolean canMove(int lastKey, int nextKey) {
        eGridSwitchType nextGridType = eGridSwitchType.forValue(getGridType(nextKey));
        if (null == nextGridType) {
            return false;
        }

        switch (nextGridType) {
            case End: {
                //走到终点时还剩下两个格子
                if (gridMap.size() != 2) {
                    return false;
                }
            }
            break;
            case Blank:
                return false;
            default:
                break;
        }

        eGridSwitchType lastGridType = eGridSwitchType.forValue(getGridType(lastKey));
        if (null == lastGridType) {
            return false;
        }

        List<Integer> deviations = calcKeyDeviation(lastKey, nextKey);
        int distance = Math.abs(deviations.get(0) + deviations.get(1));
        if (distance != 1) {
            //只能移动到相邻的格子
            return false;
        }
        switch (lastGridType) {
            case LimitLeft:
                return deviations.get(0) == -1;
            case LimitRight:
                return deviations.get(0) == 1;
            case LimitUp:
                return deviations.get(1) == 1;
            case LimitDown:
                return deviations.get(1) == -1;
            default:
                return true;
        }
    }

    private List<Integer> calcKeyDeviation(int lastKey, int nextKey) {
        List<Integer> deviations = new ArrayList<>();

        deviations.add(nextKey / 10000 - lastKey / 10000);
        deviations.add(nextKey % 10000 - lastKey % 10000);

        return deviations;
    }

    private boolean isStartGrid(int key) {
        Integer gridType = getGridType(key);
        return gridType == eGridSwitchType.Start.getValue();
    }

    private List<Integer> move(int nextKey) {
        List<Integer> lastGridAttr = new ArrayList<>();

        lastGridAttr = this.gridMap.get(this.nowGridKey);
        this.gridMap.remove(this.nowGridKey);
        this.movedRoad.add(nextKey);
        this.nowGridKey = nextKey;
        this.moveTry++;
        return lastGridAttr;
    }

    private List<Integer> getAroundKeys(int key) {
        List<Integer> keys = new ArrayList<>();
        Integer aroundKey = key + 1;
        if (this.gridMap.containsKey(aroundKey)) {
            keys.add(aroundKey);
        }
        aroundKey = key + 10000;
        if (this.gridMap.containsKey(aroundKey)) {
            keys.add(aroundKey);
        }

        if (key % 10000 > 0) {
            aroundKey = key - 1;
            if (this.gridMap.containsKey(aroundKey)) {
                keys.add(aroundKey);
            }
        }
        if (key / 10000 > 0) {
            aroundKey = key - 10000;
            if (this.gridMap.containsKey(aroundKey)) {
                keys.add(aroundKey);
            }
        }
        return keys;
    }

    private List<Integer> getAroundKeysByRandom(int key) {
        List<Integer> keys = new ArrayList<>();
        keys.add(key + 1);
        keys.add(key + 10000);

        if (key % 10000 > 0) {
            keys.add(key - 1);
        }
        if (key / 10000 > 0) {
            keys.add(key - 10000);
        }

        Collections.shuffle(keys);
        return keys;
    }

    private void moveBack(List<Integer> gridAttr, int lastKey) {
        this.nowGridKey = lastKey;
        this.gridMap.put(lastKey, gridAttr);
        this.movedRoad.remove(this.movedRoad.size() - 1);
    }

    public String remainGridParseToString() {
        String remainGrid = getMoveRoadString();

        for (Integer key : this.gridMap.keySet()) {
            remainGrid += getKeyCoordinate(key) + "|";
        }

        return remainGrid;
    }

    private boolean isBuildEnd() {
        return this.blankNum + 1 == this.gridMap.size();
    }

    private boolean isSoloveEnd() {
        if (this.blankNum + 1 == this.gridMap.size()) {
            if (this.haveEnd) {
                return getGridType(this.nowGridKey) == eGridSwitchType.End.getValue();
            }
            return true;
        }
        return false;
    }

    private String getKeyCoordinate(int key) {
        return String.valueOf(key / 10000) + "," + String.valueOf(key % 10000);
    }

    private boolean dfsSolove(int deepAmount) {
        String visitKey = remainGridParseToString();
        if (this.dfsVisit.containsKey(visitKey)) return false;
        dfsVisit.put(visitKey, 1);
        if (isSoloveEnd()) {
            return true;
        }
        for (Integer nextKey : getAroundKeysByRandom(this.nowGridKey)) {
            if (!canMove(this.nowGridKey, nextKey))
                continue;
            int lastKey = this.nowGridKey;
            List<Integer> gridAttr = move(nextKey);
            int nextGridType = getGridType(nextKey);
            if (dfsSolove(deepAmount + 1)) {
                System.out.printf("deep=%d from (%d,%d) to (%d,%d)", deepAmount + 1, lastKey / 10000, lastKey % 10000, nextKey / 10000, nextKey % 10000);
                answerList.get(deepAmount + 1).set(0, nextKey / 10000);//设置坐标x
                answerList.get(deepAmount + 1).set(1, nextKey % 10000);//坐标y
                answerList.get(deepAmount + 1).set(2, nextGridType);//格子属性
                return true;
            }
            moveBack(gridAttr, lastKey);
        }

        return false;
    }

    private boolean dfsBuild(int deepAmount) {
        String visitKey = remainGridParseToString();
        if (this.dfsVisit.containsKey(visitKey)) return false;
        dfsVisit.put(visitKey, 1);

        //判断是否结束
        if (isBuildEnd()) {
            return true;
        }
        for (Integer nextKey : getAroundKeysByRandom(this.nowGridKey)) {
            if (!canMove(this.nowGridKey, nextKey))
                continue;
            int lastKey = this.nowGridKey;
            List<Integer> gridAttr = move(nextKey);
            int nextGridType = getGridType(nextKey);
            if (dfsBuild(deepAmount + 1)) {
                answerList.get(deepAmount + 1).set(0, nextKey / 10000);//设置坐标x
                answerList.get(deepAmount + 1).set(1, nextKey % 10000);//坐标y
                if (answerList.get(deepAmount + 1).get(2) == 0) {
                    answerList.get(deepAmount + 1).set(2, nextGridType);//格子属性
                }
                return true;
            }
            moveBack(gridAttr, lastKey);
        }

        return false;
    }

    public boolean solove() {
        if (!this.dfsSolove(0))
            return false;
        this.dfsVisit.clear();
        return true;
    }

    private boolean initStage() {
        if (this.length * this.wide - this.blankNum - 2 < 0) {
            //System.out.printf("除去起点终点,空白的格子数不能超过总格子");
            return false;
        }

        if (this.length * this.wide - this.blankNum - 2 < this.directionNum) {
            //System.out.printf("除去起点终点和空白格,有方向的格子数不能超过总格子");
            return false;
        }

        this.movedRoad.clear();
        this.answerList.clear();
        this.gridMap.clear();
        this.dfsVisit.clear();
        this.moveTry = 0;

        if (this.startKey <= -1) {
            this.nowGridKey = getKey(random.nextInt(length), random.nextInt(wide));
        } else {
            this.nowGridKey = this.startKey;
        }
        this.movedRoad.add(this.nowGridKey);

        for (int i = 0; i < length * wide - blankNum; i++) {
            List<Integer> gridAttr = new ArrayList<>();
            gridAttr.add(0);
            gridAttr.add(0);
            gridAttr.add(0);
            this.answerList.add(gridAttr);
        }
        this.answerList.get(0).set(0, this.nowGridKey / 10000);
        this.answerList.get(0).set(1, this.nowGridKey % 10000);
        this.answerList.get(0).set(2, eGridSwitchType.Start.getValue());
        if (this.haveEnd) {
            this.answerList.get(this.answerList.size() - 1).set(2, eGridSwitchType.End.getValue());
        }

        for (int x = 0; x < length; x++) {
            for (int y = 0; y < wide; y++) {
                List<Integer> gridAttr = new ArrayList<>();
                gridAttr.add(x);
                gridAttr.add(y);
                int key = getKey(x, y);
                gridAttr.add(eGridSwitchType.NotLimit.getValue());
                this.gridMap.put(key, gridAttr);
            }
        }

        return true;
    }

    public boolean buildRandomStage() {
        if (!initStage()) {
            return false;
        }

        if (!dfsBuild(0)) {
            return false;
        }

        if (this.directionNum > 0) {
            List<Integer> directionList = getRandomDistinctInteger(1, this.answerList.size() - 2, this.directionNum);
            for (int idx : directionList) {
                int lastKey = getKey(this.answerList.get(idx).get(0), this.answerList.get(idx).get(1));
                int nextKey = getKey(this.answerList.get(idx + 1).get(0), this.answerList.get(idx + 1).get(1));
                List<Integer> deviations = calcKeyDeviation(lastKey, nextKey);
                if (deviations.get(0) == -1) {
                    this.answerList.get(idx).set(2, eGridSwitchType.LimitLeft.getValue());
                } else if (deviations.get(0) == 1) {
                    this.answerList.get(idx).set(2, eGridSwitchType.LimitRight.getValue());
                } else if (deviations.get(1) == 1) {
                    this.answerList.get(idx).set(2, eGridSwitchType.LimitUp.getValue());
                } else if (deviations.get(1) == -1) {
                    this.answerList.get(idx).set(2, eGridSwitchType.LimitDown.getValue());
                }
            }
        }

        this.gridMap.clear();
        for (List<Integer> gridAttr : this.answerList) {
            this.gridMap.put(getKey(gridAttr.get(0), gridAttr.get(1)), gridAttr);
        }

        return true;
    }

    public String getMoveRoadString() {
        String moveRoadStr = "";
        for (Integer key : this.movedRoad) {
            moveRoadStr += getKeyCoordinate(key) + "-";
        }
        return moveRoadStr;
    }

    public String getStageString() {
        String stageString = "";
        List<Integer> keys = new ArrayList<>(this.gridMap.keySet());
        keys.sort(Integer::compareTo);
        for (Integer key : keys) {
            stageString += this.gridMap.get(key).toString() + "-";
        }
        return stageString;
    }

    public List<List<Integer>> getAnswerList() {
        return answerList;
    }

    public List<Integer> getRandomDistinctInteger(int begin, int end, int takeNum) {
        List<Integer> takeList = new ArrayList<>();
        if (begin > end || begin < 0 || takeNum <= 0) {
            return takeList;
        }
        if (begin == end) {
            takeList.add(begin);
        } else {
            int range = end - begin + 1;
            int randomNum = random.nextInt(1000000000) + 1000000000;
            for (int i = 0; i < range && takeNum > 0; i++) {
                if (randomNum % (range - i) < takeNum) {
                    takeList.add(i + begin);
                    takeNum--;
                }
            }
        }

        return takeList;
    }

    public String getParam() {
        String param = this.length + "|" + this.wide + "|" + this.blankNum + "|" + this.directionNum + "|" + this.haveEnd;
        return param;
    }

    @Override
    public int verifyOperation(SmallGameProto.SmallGameChallengeSuccessReqMsg reqMsg) {
        if (!reqMsg.hasGridSwitchOp()) {
            return GameErrorCode.E_SMALLGAME_GRID_SWITCH_FAILURE;
        }
        Integer lastKey = -1;
        for (SmallGameProto.GridSwitchOperationInfo operationInfo : reqMsg.getGridSwitchOp().getOperationListList()) {
            Integer nextKey = getKey(operationInfo.getX(), operationInfo.getY());
            if (lastKey == -1) {
                if (!isStartGrid(nextKey)) {
                    return GameErrorCode.E_SMALLGAME_GRID_SWITCH_FAILURE;
                }
                lastKey = nextKey;
                continue;
            }
            if (!canMove(lastKey, nextKey))
                return GameErrorCode.E_SMALLGAME_GRID_SWITCH_FAILURE;

            //移动后移除格子
            this.gridMap.remove(lastKey);
            lastKey = nextKey;
        }
        if (!isBuildEnd()) {
            return GameErrorCode.E_SMALLGAME_GRID_SWITCH_FAILURE;
        }
        return 0;
    }

    @Override
    public eLogMoneyType getLogMoneyType() {
        return eLogMoneyType.SmallGameChallengeLineSwitch;
    }

    @Override
    public SmallGameStageInterface initByConfig(SmallGameConfig config) {
        if (null == config) {
            return null;
        }

        if (StringUtils.isNullOrEmpty(config.getStageData())) {
            return null;
        }

        List<List<Integer>> gridList = JSON.parseObject(config.getStageData(), new TypeReference<List<List<Integer>>>() {
        });

        for (List<Integer> gridAttr : gridList) {
            if (gridAttr.size() < 2) {
                continue;
            } else {
                Integer gridType = gridAttr.get(2);
                if (gridType == eGridSwitchType.Blank.getValue()) {
                    //空白格不放进来
                    continue;
                }

                if (gridType == eGridSwitchType.Start.getValue()) {
                    this.nowGridKey = getKey(gridAttr.get(0), gridAttr.get(1));
                    this.startKey = this.nowGridKey;
                    this.answerList.add(gridAttr);
                    this.movedRoad.add(this.nowGridKey);
                }
            }
            Integer key = getKey(gridAttr.get(0), gridAttr.get(1));
            this.gridMap.put(key, gridAttr);
        }
        return this;
    }
}
